//      data.h
//      
//      Copyright 2012 tqlong <tqlong@espada>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.

#ifndef __DATA_H
#define __DATA_H
#include "stl-helper.h"
#include "sparse_vec.h"

struct simple_datum_t {
  vd_t x_;
  double y_;
  
  typedef vd_t x_type;
  
  simple_datum_t () {}
  simple_datum_t (const x_type& x, double y) : x_(x), y_(y) {}
  
  const vd_t& x() const { return x_; }
  double y() const { return y_; }
  void save(std::ostream& out, bool labeled, const std::string& separator) const;
};

struct sparse_datum_t {
  typedef SparseVector x_type;
  
  x_type x_;
  double y_;  
  
  sparse_datum_t () {}
  sparse_datum_t (const x_type& x, double y) : x_(x), y_(y) {}
  const x_type& x() const { return x_; }
  double y() const { return y_; }
  void save(std::ostream& out, bool labeled, const std::string& separator) const;
};

template <typename READER>
class GeneralData 
{
public:
  typedef READER reader_t;
  typedef typename reader_t::datum_t datum_t;
  typedef typename reader_t::param_t param_t;
  
  GeneralData(const param_t& param) : reader_(param)  { }
  
  const datum_t& operator() (int i) { return reader_.get_datum(i); }
  
  int size() { return reader_.size(); }
  
  int dim() { return reader_.dim(); }
  
  bool isLabeled() { return reader_.isLabeled(); }
protected:
  reader_t reader_;
};

template <typename D>
class DataSubset
{
public:
  typedef typename D::datum_t datum_t;
  DataSubset(D& d) : d_(d) {}

  DataSubset(D& d, const vi_t& idx) : d_(d), idx_(idx) {}

  void randperm() { idx_ = RAND::randperm(d_.size()); }

  void subset(const vi_t& idx) { idx_ = idx; }

  const datum_t& operator() (int i) const { return d_(idx_[i]); }

  bool isLabeled() const { return d_.isLabeled(); }

  int size() const { return sz(idx_); }

  int dim() const { return d_.dim(); }
protected:
  D& d_;
  vi_t idx_;
};

class DataReader
{
public:
  // save in tab format with 
  //   first line: N D
  //   following line: y x
  template <typename D>
  static void save(const std::string& file, D& data) {
    std::ofstream output;
    FileAccess::open2write(output, file);
    int N = data.size();
    //int D = data.dim();
    //output << N << "\t" << D << "\t" << (data.isLabeled() ? "1" : "0") << ENDL;
    for (int i = 0; i < N; i++) {
      const typename D::datum_t& d_i = data(i);
      d_i.save(output, data.isLabeled(), "\t");
      output << ENDL;
    }
    output.close();
  }
};

class SimplestMultiFileReader
{
public:
  typedef simple_datum_t           datum_t;
  typedef std::vector<std::string> param_t;
  
  SimplestMultiFileReader(const std::vector<std::string>& fname);
  const datum_t& get_datum (int i);
  bool isLabeled() const ;
  int dim() const ;
  int size() const ;
protected:
  int dim_;
  bool labeled_;
  param_t fname_;
  datum_t tmp_;

  static vd_t line2doubles(const std::string& line);
};

class SimplestReader
{
public:
  typedef simple_datum_t datum_t;
  typedef std::string    param_t;
  
  SimplestReader (const param_t& param);

  const datum_t& get_datum(int i) const { return d_[i]; }

  int size() const { return sz(d_); }

  int dim() const { return dim_; }

  bool isLabeled() const { return labeled_; }
protected:
  std::vector<datum_t> d_;
  int dim_;
  bool labeled_;
};

class SimplestSparseReader
{
public:
  typedef sparse_datum_t datum_t;
  typedef std::string    param_t;
  
  SimplestSparseReader (const param_t& param);

  const datum_t& get_datum(int i) const { return d_[i]; }

  int size() const { return sz(d_); }

  int dim() const { return dim_; }

  bool isLabeled() const { return labeled_; }
protected:
  std::vector<datum_t> d_;
  int dim_;
  bool labeled_;
};

class MatrixData {
 public:
  const arma::mat& data() const { return data_; }
  const arma::vec& label() const { return label_; }
  arma::mat& data() { return data_; }
  arma::vec& label() { return label_; }

  int size() const { return data_.n_rows; }
  int dim() const { return data_.n_cols; }

  arma::vec f(int j) const { return data_.col(j); }
  arma::vec x(int i) const { return arma::trans(data_.row(i)); }
  double x(int i, int j) const { return data_(i, j); }
  double y(int i) const { return label_(i); }  

  void save(const std::string& outputFile) const;

  void to_unit_norm();

  void get_norm_scale(arma::rowvec& scale, arma::rowvec& offset) const;
  void apply_norm_scale(const arma::rowvec& scale, const arma::rowvec& offset, MatrixData& new_m) const;
  void apply_norm_scale(const arma::rowvec& scale, const arma::rowvec& offset);

  void split_train_test(const vi_t& test_idx, MatrixData& train, MatrixData& test, bool normalize = true) const;
	
	static void load_idx(const std::string& idxFile, vi_t& idx);
	static void save_idx(const std::string& idxFile, const vi_t& idx);
  
 protected:
  arma::mat data_;
  arma::vec label_;
};

class TextReader {
 public:
  std::map<std::string, double> label_map_;
  std::string delimiter_;
  
  void read(const std::string& inputFile, MatrixData& m, bool add_bias = false);
  void read_last_label(const std::string& inputFile, MatrixData& m);
  void default_label_map();
};

class ClassPerf {
 public:
  ClassPerf();
  void add(const arma::vec& truth, const arma::vec& class_out);
 public:
  double n, n_pos, n_neg;
  double n_correct, n_pos_correct, n_neg_correct;
	double sse;

  double accuracy() const { return n_correct / n; }
  double sensitivity() const { return n_pos_correct / n_pos; }
  double specificity() const { return n_neg_correct / n_neg; }
	double mse() const { return sse / n; }
};

#endif
